﻿#include "NetServer.h"

#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>


NetServer* NetServer::pNetServerInstance_ = NULL;
NetServer::NetServer() {}
NetServer::~NetServer() {}

void NetServer::NSDataCB(struct mg_connection* nc, int ev, void* ev_data) {
  NetServer* handle = (NetServer*)nc->user_data;
  struct http_message* hm = (struct http_message*)ev_data;
  std::string url;
  Json::Value rsq;
  switch (ev) {
  case MG_EV_HTTP_REQUEST:
    url = std::string(hm->uri.p, hm->uri.len);
    static struct mg_serve_http_opts opts = { 0 };
    opts.enable_directory_listing = "yes";
    opts.document_root = "www";
    if (mg_vcmp(&hm->uri, "/live/stream.flv") == 0) {
      handle->AddFlvSession(nc, hm);
      return;
    }
    if (mg_vcmp(&hm->method, "POST") == 0 && 0 == url.compare(handle->url_)) {
      std::string body(hm->body.p, hm->body.len);
      rsq["code"] = 0;
      rsq["msg"] = "OK";
      if (handle->cb_)
      {
        Json::Value v;
        if (CUitl::str2json(body, v))
        {
          try
          {
            float x = v["x"].asFloat();
            float y = v["y"].asFloat();
            float w = v["w"].asFloat();
            float h = v["h"].asFloat();
            handle->cb_(x, y, w, h);
          }
          catch (const std::exception& e)
          {
            // std::cerr << e.what() << '\n';
            rsq["code"] = -1;
            rsq["msg"] = "ERROR";
          }
        }
      }
      handle->HttpRspJson(nc, rsq);
      return;
    }
    mg_serve_http(nc, hm, opts);
    break;
  case MG_EV_POLL:
    handle->FlvProcess();
    break;
  case MG_EV_CLOSE:
    handle->CloseFlvSession(nc, hm);
    break;
  }
}

void NetServer::HttpRspJson(mg_connection* nc, Json::Value& rsq) {
  if (!nc) {
    return;
  }

  std::string data = CUitl::json2str(rsq);
  mg_printf(nc,
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: application/json\r\n"
    "Connection: close\r\n"
    "Content-Length: %u\r\n\r\n"
    "%s\r\n",
    (uint32_t)data.size(), data.c_str());
  nc->flags |= MG_F_SEND_AND_CLOSE;
}
bool NetServer::unInit()
{
  running_ = false;
  thread_->detach();
  url_.clear();
  cb_ = nullptr;
  return true;
}
bool NetServer::Init(int port, const char* url, WebStreamCB cb) {

  m_port_ = port;
  url_ = std::string(url);
  cb_ = cb;
  thread_ = new std::thread([&] {

    pthread_setname_np(pthread_self(), "NetServer");
    char portaddr[128];
    sprintf(portaddr, "%d", m_port_);
    mg_mgr_init(&mgr_, NULL);
    nc_ = mg_bind(&mgr_, portaddr, NetServer::NSDataCB);
    if (nc_ == NULL) {
      std::cout << "Failed to create listener\n" << std::endl;
    }
    nc_->user_data = (void*)this;
    mg_set_protocol_http_websocket(nc_);
    running_ = true;
    while (running_) {
      mg_mgr_poll(&mgr_, 5);
    }
    mg_mgr_free(&mgr_);
    });

  return true;
}

void NetServer::SendVideoFrame(const void* data, int len, int codec) {

  if (!running_)
  {
    return;
  }

  std::lock_guard<std::mutex> lk(mutex_);
  for (const auto& connection : flv_conn_map_) {
    stflvSessionInfo* p = (stflvSessionInfo*)connection.second;
    static uint32_t pts;
    const uint8_t* nalu = (const uint8_t*)data + 4;
    flv_muxer_h264_nalu(p->m, nalu, len - 4, p->pts, p->pts);
    uint8_t type = *nalu & 0x1f;
    if (type > 0 && type < 6) {
      p->pts = CUitl::gettimestatmpms();
    }
  }
}

void NetServer::AddFlvSession(struct mg_connection* nc,
  struct http_message* hm) {
  std::lock_guard<std::mutex> lk(mutex_);
  mg_printf(nc,
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: video/x-flv\r\n"
    "Connection: Keep-Alive\r\n"
    "Transfer-Encoding: chunked\r\n\r\n");
  SendFlvHeaderTag(nc);
  stflvSessionInfo* pSession =
    (stflvSessionInfo*)malloc(sizeof(stflvSessionInfo));
  memcpy(pSession->url, hm->uri.p, hm->uri.len);
  pSession->queue = new std::list<std::string>;
  pSession->mutex = new std::mutex;
  flv_conn_map_[nc] = (void*)pSession;
  pSession->m = CreateFlvMuxer(pSession);
}

void NetServer::CloseFlvSession(struct mg_connection* nc,
  struct http_message* hm) {
  std::lock_guard<std::mutex> lk(mutex_);
  for (const auto& connection : flv_conn_map_) {
    if (connection.first == nc) {
      stflvSessionInfo* pSession = (stflvSessionInfo*)connection.second;
      if (pSession) {
        pSession->queue->clear();
        flv_muxer_destroy(pSession->m);
        delete pSession->queue;
        delete pSession->mutex;
        free(pSession);
        flv_conn_map_.erase(connection.first);
        break;
      }
    }
  }
}

void NetServer::FlvProcess() {
  std::lock_guard<std::mutex> lk(mutex_);
  for (const auto& connection : flv_conn_map_) {
    stflvSessionInfo* p = (stflvSessionInfo*)connection.second;
    if (!p) {
      printf("%s %d\n", __FUNCTION__, __LINE__);
      continue;
    }
    if (p->queue->empty()) continue;
    std::string data = p->queue->front();
    p->queue->pop_front();
    std::stringstream hexSize;
    hexSize << std::hex << data.size();
    std::string datatunck = hexSize.str() + "\r\n";
    datatunck.append(data);
    datatunck.append("\r\n");
    SendDataFlv(connection.first, datatunck.c_str(), datatunck.size(), false);
  }
}

bool NetServer::SendDataFlv(struct mg_connection* nc, const char* pData, int len,
  bool needheader) {
  if (!needheader) {
    mg_send(nc, pData, len);
    return true;
  }
  std::stringstream decSize;
  decSize << std::dec << len;
  std::string chunkHeader = decSize.str() + "\r\n";
  mg_send(nc, chunkHeader.c_str(), chunkHeader.size());
  if (pData) {
    mg_send(nc, pData, len);
    mg_send(nc, "\r\n", 2);
  }
  return true;
}
bool NetServer::SendFlvHeaderTag(struct mg_connection* nc) {
  uint8_t header[9];
  header[0] = 'F';  // FLV signature
  header[1] = 'L';
  header[2] = 'V';
  header[3] = 0x01;  // File version
  header[4] = 0x01;  // Type flags (audio & video)
  header[5] = 0x00;
  header[6] = 0x00;
  header[7] = 0x00;  // File version
  header[8] = 0x09;
  SendDataFlv(nc, (const char*)header, sizeof(header));
  header[0] = 0x00;  // FLV signature
  header[1] = 0x00;
  header[2] = 0x00;
  header[3] = 0x00;  // File version
  SendDataFlv(nc, (const char*)header, 4);
  return true;
}
int NetServer::OnFlvPack(void* flv, int type, const void* data, size_t bytes,
  uint32_t timestamp) {
  char* pFrame = (char*)malloc(bytes + 16);
  int len = flv_writer_input_mem(flv, type, data, bytes, timestamp, pFrame);
  stflvSessionInfo* pFlv = (stflvSessionInfo*)flv;
  std::string strdata = std::string((const char*)pFrame, len);
  pFlv->queue->push_back(strdata);
  free(pFrame);
  return 0;
}
flv_muxer_t* NetServer::CreateFlvMuxer(void* pUsrData) {
  flv_muxer_t* m = flv_muxer_create(&NetServer::OnFlvPack, pUsrData);
  struct flv_metadata_t metadata = { 0 };
  metadata.videocodecid = 7;
  metadata.videodatarate = 4096.0;
  metadata.framerate = 30;
  metadata.width = 1280;
  metadata.height = 768;
  metadata.audiocodecid = 0;
  flv_muxer_metadata(m, &metadata);
  return m;
}